package archer.back.controllers

import java.sql.Timestamp
import java.text.SimpleDateFormat
import java.util.Date

import archer.back.models.CrawlActionWrap
import utils._
import models.TableData

import scala.concurrent.{ ExecutionContext, Future }
import cats.implicits._
import io.circe.syntax._
import io.circe.generic.auto._
import org.xarcher.emiya.utils.{
  EmbeddedServer,
  FutureLimitedGen,
  FutureTimeLimitedGen
}
import play.api.http.Status

import scala.util.Try

class PlanService(
  commonDB: CommonDB,
  elasticService: ElasticService,
  futureTimeLimitedGen: FutureTimeLimitedGen,
  embeddedServer: EmbeddedServer,
  futureLimitedGen: FutureLimitedGen)(implicit ec: ExecutionContext)
  extends ArcherHelper {

  import models.CrawlTables._
  import profile.api._

  def list: V.ResultT[TableData[Seq[CrawlPlanRow]]] = {
    val resultF = futureLess.lift {
      val action =
        CommonResult.commonResult(CrawlPlan, CrawlPlan, SlickParam.empty)
      val (list, count) = futureLess.unlift(commonDB.db.run(action))
      TableData(data = list, count = Option(count))
    }
    commonCatch(resultF)
  }

  def retrieve(id: Int): V.ResultT[TableData[CrawlPlanRow]] = {
    etLess.lift {
      val action = CrawlPlan.filter(_.id === id).result.headOption
      val rowOpt = etLess.unlift(commonCatch(commonDB.db.run(action)))
      val row = etLess.unlift(
        V.fromOption(
          rowOpt,
          ErrorResult("找不到对应的抓取计划", code = Status.NOT_FOUND)))
      TableData(data = row, count = Option(1))
    }
  }

  def update(plan: CrawlPlanRow): V.ResultT[TableData[Option[String]]] = {
    etLess.lift {
      val planOptAction = CrawlPlan.filter(_.id === plan.id).result.headOption
      val rowOpt = etLess.unlift(commonCatch(commonDB.db.run(planOptAction)))
      val row = etLess.unlift(
        V.fromOption(
          rowOpt,
          ErrorResult("找不到对应的抓取计划", code = Status.NOT_FOUND)))
      etLess.unlift(
        commonCatch(
          commonDB.db.run(
            CrawlPlan
              .filter(_.id === plan.id)
              .update(
                row.copy(
                  name = plan.name,
                  rootUrl = plan.rootUrl,
                  maxDeep = plan.maxDeep,
                  maxOccurs = plan.maxOccurs,
                  minuteLimit = plan.minuteLimit))))): Int
      TableData.msg("修改抓取计划成功")
    }
  }

  def create(plan: CrawlPlanRow): V.ResultT[TableData[Option[String]]] = {
    etLess.lift {
      val rowOpt = etLess.unlift(
        commonCatch(
          commonDB.db.run(CrawlPlan
            .returning(CrawlPlan.map(_.id))
            .into((model, id) => model.copy(id = id)) += plan))): CrawlPlanRow
      TableData.msg("新增抓取计划成功")
    }
  }

  def delete(id: Int): V.ResultT[TableData[Option[String]]] = {
    etLess.lift {
      val rows = etLess.unlift(
        commonCatch(commonDB.db.run(CrawlPlan.filter(_.id === id).delete))): Int
      val result = if (rows > 0) {
        V.rightT(TableData.msg("删除抓取计划成功"))
      } else {
        V.leftT[TableData[Option[String]]](
          ErrorResult("找不到需要删除的抓取计划", code = Status.NOT_FOUND))
      }
      etLess.unlift(result)
    }
  }

  def test01Start(id: Int): V.ResultT[TableData[Option[String]]] = {
    etLess.lift {
      val rowOpt = etLess.unlift(
        commonCatch(
          commonDB.db.run(CrawlPlan.filter(_.id === id).result.headOption)))
      val plan = etLess.unlift(
        V.fromOption(
          rowOpt,
          ErrorResult("找不到需要启动的抓取计划", code = Status.NOT_FOUND)))

      val crawlActionAction = CrawlAction
        .returning(CrawlAction.map(_.id))
        .into((model, id) => model.copy(id = id)) +=
        CrawlActionRow(
          id = -1,
          isFinish = false,
          isRunning = true,
          planId = plan.id,
          lastestRunTime = new Timestamp(new Date().getTime),
          finishedTime = Option.empty)

      //etLess.unlift(commonCatch(commonDB.db.run(CrawlPlan.filter(_.id === plan.id).map(_.isRunning).update(true).transactionally))): Int
      val crawlAction =
        etLess.unlift(commonCatch(commonDB.db.run(crawlActionAction)))

      val timePool =
        futureTimeLimitedGen.create(plan.minuteLimit, "网页抓取时间限流", 1000)
      val occurPool = futureLimitedGen.create(plan.maxOccurs, "网页抓取并发限流")

      val num: Int = etLess.unlift(
        elasticService.initSearch(plan, crawlAction, timePool, occurPool))
      elasticService
        .crawlLoop(plan, crawlAction, timePool, occurPool)
        .map { (_: Boolean) =>
          commonCatch(commonDB.db.run({
            //CrawlPlan.filter(_.id === plan.id).map(s => s.isFinish -> s.isRunning).update(true -> false) >>
            CrawlAction
              .filter(s => s.id === crawlAction.id)
              .map(s => (s.isFinish, s.isRunning, s.finishedTime))
              .update((true, false, Option(new Timestamp(new Date().getTime))))
          }.transactionally)).leftMap { s =>
            logger.error(s.toString)
            s
          }
        }
        .failed
        .foreach { e =>
          logger.error("执行抓取任务失败", e)
        }
      TableData.msg("启动抓取计划成功")
    }
  }
  /*def start(id: Int): V.ResultT[TableData[Option[String]]] = {
    etLess.lift {
      val rowOpt = etLess.unlift(commonCatch(commonDB.db.run(CrawlPlan.filter(_.id === id).result.headOption)))
      val row = etLess.unlift(V.fromOption(rowOpt, ErrorResult("找不到需要启动的抓取计划", code = Status.NOT_FOUND)))
      etLess.unlift(commonCatch(commonDB.db.run(CrawlPlan.filter(_.id === row.id).map(_.isRunning).update(true).transactionally))): Int
      etLess.unlift(commonCatch(commonDB.db.run {
        CrawlUrls += CrawlUrlsRow(
          id = -1,
          refer = "http://www.baidu.com/",
          url = row.rootUrl,
          isFinish = false,
          isError = false,
          urlHost = Try { new URL(row.rootUrl).getHost }.toOption.filterNot(_.isEmpty),
          urlProtocol = Try { new URL(row.rootUrl).getProtocol }.toOption.filterNot(_.isEmpty),
          deep = 0,
          htmlContent = Option.empty,
          contentLength = Option.empty,
          path = List.empty[Int].asJson.noSpaces,
          responseCode = -1,
          requestTime = new Timestamp(new Date().getTime),
          planId = id)
      }.recover {
        case e: Exception =>
          e.printStackTrace()
          0
      })): Int
      val timePool = futureTimeLimitedGen.create(row.minuteLimit, "aaaa", 1000)
      val occurPool = futureLimitedGen.create(row.maxOccurs, "bbbb")
      crawlService.crawlLoop(row, timePool, occurPool).map { (_: Boolean) =>
        commonCatch(commonDB.db.run(CrawlPlan.filter(_.id === row.id).map(_.isFinish).update(true).transactionally)).leftMap { s =>
          logger.error(s.toString)
          s
        }
      }
      TableData.msg("启动抓取计划成功")
    }
  }*/
  def actionByTime(time: String, planId: Int): V.ResultT[CrawlActionRow] = {
    etLess.lift {
      val timeEt = V.fromEither(Try {
        val format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
        format.parse(time)
      }.toEither.left.map(s => ErrorResult("时间格式不符")))
      val parsedTime = etLess.unlift(timeEt)
      val query = CrawlAction
        .filter(
          s =>
            (s.planId === planId) && (s.lastestRunTime < new Timestamp(
              parsedTime.getTime)))
        .sortBy(_.lastestRunTime.desc)
      val actionOpt =
        etLess.unlift(commonCatch(commonDB.db.run(query.result.headOption)))
      val action = etLess.unlift(
        V.fromOption(actionOpt, ErrorResult(msg = "该时间之前没有运行过抓取任务")))
      action
    }
  }

  def actions(planId: Int): V.ResultT[TableData[Seq[CrawlActionWrap]]] = {
    etLess.lift {
      val query = CrawlAction.filter(s => s.planId === planId)
      val dbio = CommonResult.commonResult(
        query,
        query.sortBy(_.lastestRunTime.desc),
        SlickParam.empty)
      val actions = etLess.unlift(commonCatch(commonDB.db.run(dbio)))

      def modelWrap(actionId: Int, planId: Int) =
        commonDB.db.run(
          CrawlUrls
            .filter(s =>
              (s.finishedCrawl === false) && (s.actionId === actionId) && (s.planId === planId))
            .size
            .result)

      val futures = actions._1.map { s =>
        futureLess.lift {
          val urlsLeft = futureLess.unlift(modelWrap(s.id, s.planId))
          CrawlActionWrap(
            id = s.id,
            isFinish = s.isFinish,
            isRunning = s.isRunning,
            planId = s.planId,
            urlsLeft = urlsLeft,
            finishedTime = s.finishedTime,
            lastestRunTime = s.lastestRunTime)
        }
      }

      val actionWraps = etLess.unlift(commonCatch(Future.sequence(futures)))
      TableData(data = actionWraps, count = Option(actions._2))
    }
  }

  def actionById(actionId: Int): V.ResultT[TableData[CrawlActionRow]] = {
    etLess.lift {
      val query = CrawlAction.filter(s => s.id === actionId)
      val actionOpt =
        etLess.unlift(commonCatch(commonDB.db.run(query.result.headOption)))
      val action =
        etLess.unlift(V.fromOption(actionOpt, ErrorResult(msg = "找不到对应的抓取任务")))
      TableData(data = action, count = Option(1))
    }
  }

}
